﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace cs746
{
    class CecilAnalyzer
    {
        public static TAData Analyze(string assemblyName)
        {
            var ta = new TAData();
            var  module = ModuleDefinition.ReadModule(assemblyName);

            Console.BufferWidth = 200;
            Console.WindowWidth = 200;

            foreach (var type in module.Types)
            {
                if (type.Name == "<Module>") continue; // <Module> hidden type - exclude it
                if (type.IsInterface) ta.AddObject(type.FullName, ObjectType.Interface);
                else if (type.IsClass) ta.AddObject(type.FullName, ObjectType.Class);
                else throw new Exception();

                WriteLineToConsole(type.FullName, 0);
                
                foreach (var method in type.Methods)
                {
                    //if (method.IsConstructor) ta.AddObject(method.FullName,ObjectType.Ctor);
                    //else if (method.IsVirtual) ta.AddObject(method.FullName, ObjectType.VirtualFunction);
                    //else ta.AddObject(method.FullName, ObjectType.Function); // it's not correct 
                    //WriteLineToConsole(method.ToString(), 1);
                    
                    ta.AddEdge(type.FullName, method.FullName, EdgeType.Contains); 

                    //ParseMethodBody(ta, method, 2, new List<string> { method.ToString() }); 
                   
                    //Console.WriteLine();
                    ParseMethodBodyNew(ta, method, 1, new List<string>());
                    Console.WriteLine();

                }
                Console.WriteLine();
            }
            

            Console.ReadKey();
            return ta;

        }

        static void ParseMethodBodyNew(TAData ta, object ins, int step, List<string> callstack)
        {
            var method = ins as MethodDefinition;
            if (method != null && method.HasBody)
            {
                WriteLineToConsole(method.ToString(), step);
                if (method.IsVirtual) ta.AddObject(method.FullName, ObjectType.VirtualFunction);
                else if (method.IsConstructor) ta.AddObject(method.FullName, ObjectType.Ctor);
                else if (method.IsStatic) ta.AddObject(method.FullName, ObjectType.StaticFunction);
                else ta.AddObject(method.FullName, ObjectType.Function);

                foreach (var item in method.Body.Instructions)
                {
                    if (!(item.OpCode.Name == "call" || item.OpCode.Name == "callvirt" || item.OpCode.Name == "newobj")) 
                    { 
                        continue; 
                    }
                    if (callstack.Contains(item.Operand.ToString()))
                    {
                        WriteLineToConsole("<< Recursion >>", step);
                        ta.AddEdge(method.FullName, ((MethodDefinition)item.Operand).FullName, EdgeType.RecursiveCall); 
                    }
                    else if (item.OpCode.Name == "call" || item.OpCode.Name == "callvirt")
                    {                        
                        WriteLineToConsole(item.Operand.ToString(), step);

                        ta.AddEdge(method.FullName, ((MethodReference)item.Operand).FullName, item.OpCode.Name == "call" ? EdgeType.Call : EdgeType.VirtualCall);

                        callstack.Add(item.Operand.ToString());
                        ParseMethodBodyNew(ta, item.Operand, step + 1, callstack);
                        callstack.Remove(item.Operand.ToString());                        
                    }
                    else if (item.OpCode.Name == "newobj")
                    {
                        WriteLineToConsole(item.Operand.ToString(), step);

                        ta.AddEdge(method.FullName, ((MethodReference)item.Operand).FullName,  EdgeType.CtorCall);

                        callstack.Add(item.Operand.ToString());
                        ParseMethodBodyNew(ta, item.Operand, step + 1, callstack);
                        callstack.Remove(item.Operand.ToString());
                    }
                    //else WriteLineToConsole(item.OpCode.Name, step); 
                }
            }
            else
            {
                var methodRef = ins as MethodReference;
                if (methodRef != null)
                {
                    WriteLineToConsole(methodRef.ToString(), step);
                    ta.AddObject(methodRef.FullName, ObjectType.MethodReference);
                    //WriteLineToConsole(methodRef.Module.FullyQualifiedName.ToString(), step);
                    

                }
                else throw new Exception();
            }
        }


        //static void ParseMethodBody(TAData ta, object ins, int step, List<string> callstack)
        //{
        //    var method = ins as MethodDefinition;
        //    if (method != null && method.HasBody)
        //    {                
        //        foreach (var item in method.Body.Instructions)
        //        {
        //            if (item.OpCode.Name == "call" || item.OpCode.Name == "callvirt")
        //            {                        
        //                if (callstack.Contains(item.Operand.ToString()))
        //                {
        //                    WriteLineToConsole("<< Recursion >>", step);


        //                }
        //                else
        //                {
        //                    WriteLineToConsole(item.Operand.ToString(), step);
        //                    callstack.Add(item.Operand.ToString());
        //                    ParseMethodBody(ta, item.Operand, step + 1, callstack);
        //                    callstack.Remove(item.Operand.ToString());
        //                }
        //            }
        //            else if (item.OpCode.Name == "newobj")
        //            {
        //                WriteLineToConsole(item.Operand.ToString(), step);
        //                callstack.Add(item.Operand.ToString());
        //                ParseMethodBody(ta, item.Operand, step + 1, callstack); 
        //                callstack.Remove(item.Operand.ToString());
        //            }
        //            //else WriteLineToConsole(item.OpCode.Name, step); 
        //        }
        //    }
        //}


        static void WriteLineToConsole(string value, int numTabs)
        {
            for (int i = 0; i < numTabs; i++)
            {
                Console.Write("\t");
            }
            Console.WriteLine(value);
        }
    }
}
